﻿/*
 *
 *	C# object representation of row in 'researchRepo.files' table:
 *	
 *	Permissions are handled by storing both the public permission type for the file and the user's apparent permission type. Since public permissions
 *	are the minimum permissions for each file, public permissions may override the user permissions given to a constructor.
 *	
 *	Each RRFile is a node within a doubly-linked tree of RRFiles. Every file (except the repo root) stores a pointer to its parent directory (parent node);
 *	if a file is a directory then it stores a List of sub files (child nodes). The superDirID property is thus redundant and is not stored.
 *	
 *	All other properties map directly from table columns to equivalent properties.
 *	
 *	TODO Currently tree-related properties are public, but should probably be wrapped with some reference-swapping functionality to allow nodes to
 *	automatically maintain integrity of tree structure:
 *		SuperDir - set: remove 'this' from old this.SuperDir.SubFiles and add 'this' to new this.SuperDir.SubFiles
 *		SubFiles - hide List and instead implement AddSubFile(RRFile sub), UpdateSubFile(int index, RRfile sub), and RemoveSubFile(int index) which each
 *					update sub.SuperDir appropriately
 *
 */

using System;
using System.Linq;
using System.Collections.Generic;
using MySql.Data.Types;

namespace ResearchRepository.Models
{
	public class RRFile
	{
		public RRFile(int fID, int superDID, string fName, bool isD, MySqlDateTime dCreated, int cID, MySqlDateTime dModified, int mID,	string desc,	// DB table properties
						string cName = "", string mName = "",															// convenience properties
						RRPermissionType uPerm = RRPermissionType.None, RRPermissionType pPerm = RRPermissionType.None, // permissions
						RRFile superD = null, IEnumerable<RRFile> subF = null)											// doubly-linked tree properties
		{
			FileID = fID;
			SuperDirID = superDID;
			FileName = fName;
			IsDir = isD;
			DateCreated = dCreated;
			CreatorId = cID;
			DateModified = dModified;
			ModifierId = mID;
			Description = desc;

			CreatorName = cName;
			ModifierName = mName;

			UserPermission = uPerm;
			PublicPermission = pPerm;

			SuperDir = superD;
			SubFiles = IsDir ? subF == null ? new List<RRFile>() : new List<RRFile>(subF) : null;
		}
		public RRFile(RRFile source)
			:this(source.FileID,
					source.SuperDirID,
					source.FileName,
					source.IsDir,
					source.DateCreated,
					source.CreatorId,
					source.DateModified,
					source.ModifierId,
					source.Description,
					source.CreatorName,
					source.ModifierName,
					source.UserPermission,
					source.PublicPermission,
					source.SuperDir,
					source.SubFiles)
		{}
		public RRFile(System.Data.IDataRecord source, RRFile superD = null, IEnumerable<RRFile> subF = null)
			:this((int) source["fileID"],
					(int) source["superDirID"],
					(string) source["fileName"],
					((UInt64) source["isDir"]) == 1,
					(MySqlDateTime) source["dateCreated"],
					(int) source["creatorID"],
					(MySqlDateTime) source["dateModified"],
					(int) source["modifierID"],
					(string) source["description"],
					(string) source["creatorName"],
					(string) source["modifierName"],
					(RRPermissionType) Enum.Parse(typeof(RRPermissionType),
													// if userPermName is NULL, then user is 'guest', so use public permissions
													source["userPermName"] != DBNull.Value ? (string) source["userPermName"] : (string) source["publicPermName"],
													true),
					(RRPermissionType) Enum.Parse(typeof(RRPermissionType),
													(string) source["publicPermName"],
													true),
					superD,
					subF)
		{}

		// named alias for constructor, provides handle to simulate a function pointer to constructor
		public static RRFile construct(System.Data.IDataRecord source)
		{
			return new RRFile(source);
		}
		// provide generic instance of repo root as seed to populate file tree (public has default view on root)
		public static RRFile EmptyRoot
		{
			get
			{
				return new RRFile(-1, -1, "", true, new MySqlDateTime(), 0, new MySqlDateTime(), 0, "", "", "", RRPermissionType.View, RRPermissionType.View);
			}
		}

		public int FileID { get; set; }
		public int SuperDirID { get; set; }
		public string FileName { get; set; }
		public bool IsDir { get; set; }
		public MySqlDateTime DateCreated { get; set; }
		public int CreatorId { get; set; }
		public MySqlDateTime DateModified { get; set; }
		public int ModifierId { get; set; }
		public string Description { get; set; }
		
		public string CreatorName { get; set; }
		public string ModifierName { get; set; }

		public RRPermissionType UserPermission { get; set; }
		public RRPermissionType PublicPermission { get; set; }
		public RRPermissionType EffectivePermission
		{
			get
			{
				// public permission is minimum permission level, so: if given user permission is less than public, then use public
				return UserPermission < PublicPermission ? PublicPermission : UserPermission;
			}
		}

		public RRFile SuperDir { get; set; }			// TODO tree management
		public List<RRFile> SubFiles { get; set; }		// TODO tree management
		// output does not have a slash at start of path, and has a slash at the end only if the file is a directory ie: a/b/.../y/z[/]
		public string FilePath
		{
			get
			{
				return (SuperDir == null ? "" : SuperDir.FilePath) + FileName + (IsDir && FileID != EmptyRoot.FileID ? "/" : "");
			}
		}

		// recursively check equality by value, down to a specified depth
			// negative depth checks full equality for the entire subtree rooted at 'this'
		public bool Equals(RRFile f, int depth = -1)
		{
			bool eq = FileID == f.FileID && SuperDirID == f.SuperDirID && FileName == f.FileName && IsDir == f.IsDir
						&& DateCreated.Value == f.DateCreated.Value && CreatorId == f.CreatorId
						&& DateModified.Value == f.DateModified.Value && ModifierId == f.ModifierId
						&& UserPermission == f.UserPermission && PublicPermission == f.PublicPermission;
			if (!eq || depth == 0)
			{
				return eq;
			}
			if (SubFiles.Count != f.SubFiles.Count)
			{
				return false;
			}
			for (int i = 0; i < SubFiles.Count && eq; i++)
			{
				eq &= SubFiles[i].Equals(f.SubFiles[i], depth > 0 ? depth-1 : depth);
			}
			return eq;
		}
	}
}